Introduction

Bikeshare systems are an excellent, low-carbon approach to moving people from point A to B in cities. However, they have unique challenges related to fluctuations in supply and demand of bikes over time and across space. At the core of any successful bikeshare program is a balanced inventory, where all docks have bikes to ride and spots to dock available. Anticipating user demand in these systems allows administrators to move bikes ahead of that demand.

In order to effectively balance the system, systems administrators should focus on moving bikes on either side of the the rush hour peaks and bringing bikes from docks at fringe stations to core stations. One way to do this would be to ‘gamify’ balancing for riders through incentivizing riders to bring bikes back to their origin stations at the end of the day and/or incentivizing users to move bikes from full stations to empty stations during off-peak hours to reduce need of trucks bringing bikes to and fro. To manage the remainder of the re-balancing need, bikeshare administrators should direct a small crew running a fleet of trucks to collect bikes and rebalance them according to projected future demand. Predicting demand two weeks into the future would balance the competing needs of accuracy and proactive scheduling of employees.

Setup

Data

dat2 <- dat %>%
  mutate(interval60 = floor_date(start_time, unit = "hour"),
         interval15 = floor_date(start_time, unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE)) %>% 
         filter(end_lon != "", end_lat != "",
         start_lon != "", start_lat != "")

Import Census Info

phillyCensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2021, 
          state = "PA", 
          geometry = TRUE, 
          county=c("Philadelphia"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  dplyr::select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
phillyTracts <- 
  phillyCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  dplyr::select(GEOID, geometry) %>% 
  st_sf
dat_census <- st_join(dat2 %>% 
          filter(is.na(start_lon) == FALSE &
                   is.na(start_lat) == FALSE &
                   is.na(end_lat) == FALSE &
                   is.na(end_lon) == FALSE) %>%
          st_as_sf(., coords = c("start_lon", "start_lat"), crs = 4326),
        phillyTracts %>%
          st_transform(crs=4326),
        join=st_intersects,
              left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(from_longitude = unlist(map(geometry, 1)),
         from_latitude = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  dplyr::select(-geometry)%>%
  st_as_sf(., coords = c("end_lon", "end_lat"), crs = 4326) %>%
  st_join(., phillyTracts %>%
            st_transform(crs=4326),
          join=st_intersects,
          left = TRUE) %>%
  rename(Destination.Tract = GEOID)  %>%
  mutate(end_lon = unlist(map(geometry, 1)),
         end_lat = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  dplyr::select(-geometry)

Describe and Explore the Data

Import Weather Data

weather.Panel <- 
  riem_measures(station = "PHL", date_start = "2023-05-01", date_end = "2023-06-04") %>%
  dplyr::select(valid, tmpf, p01i, sknt)%>%
  replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(week = week(interval60),
           dotw = wday(interval60, label=TRUE)) %>%
    group_by(interval60) %>%
    summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() + 
  labs(title="Percipitation", x="Hour", y="Precipitation") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() + 
    labs(title="Temperature", x="Hour", y="Temperature") + plotTheme,
  top="Weather Data - Philadelphia PHL - May-June, 2023")


Here is the weather data, showing how temperature and wind speed are somewhat tied together and fluctuate with the day-night swing.

ggplot(dat_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n))+
  labs(title="Bike share trips per hr. Philadelphia, May-June, 2023",
       x="Date", 
       y="Number of trips")+
  plotTheme


This plot shows a relatively consistent daily fluctuation, with two peaks on each day of the work week - one in the morning and a larger one in the evening rush hour.

Also visible here is the relatively lower ridership on weekends. Particularly notable on May 13 and 20.

dat_census %>%
        mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
         group_by(interval60, start_station, time_of_day) %>%
         tally()%>%
  group_by(start_station, time_of_day)%>%
  summarize(mean_trips = mean(n))%>%
  ggplot()+
  geom_histogram(aes(mean_trips), binwidth = 1)+
  labs(title="Mean Number of Hourly Trips Per Station. Philadelphia, May, 2023",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day)+
  plotTheme


Mid-day and PM rush have more normal distributions, whereas AM rush and Overnight are skewed towards mostly one or two trips.

ggplot(dat_census %>%
         group_by(interval60, start_station) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 1)+
  labs(title="Bike share trips per hr by station. Philadelphia, May, 2023",
       x="Trip Counts", 
       y="Number of Stations")+
  plotTheme


Overall, the most frequent count is a single trip.

dat_census <- dat_census %>%
  mutate(start_time = as.POSIXct(start_time, format = "%m/%d/%Y %H:%M"))

# ggplot(dat_census %>% mutate(hour = hour(start_time)))+
#      geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
#   labs(title="Bike share trips in Philadelphia, by day of the week, May, 2018",
#        x="Hour", 
#        y="Trip Counts")+
#      plotTheme


ggplot(dat_census %>% 
         mutate(hour = hour(start_time),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
     geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
  labs(title="Bike share trips in Philadelphia - weekend vs weekday, May, 2018",
       x="Hour", 
       y="Trip Counts")+
     plotTheme


From this graph we can see one peak per day on weekends versus two on weekdays. On average, ridership is lower on weekends. One key takeaway from this graph is the greater need for balancing the supply of bikes on weekdays than on weekends.

ggplot()+
  geom_sf(data = phillyTracts %>%
          st_transform(crs=4326))+
  geom_point(data = dat_census %>% 
            mutate(hour = hour(start_time),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
                time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
              group_by(start_station, from_latitude, from_longitude, weekend, time_of_day) %>%
              tally(),
            aes(x=from_longitude, y = from_latitude, color = n), 
            fill = "transparent", alpha = 0.4, size = 0.3)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
  xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
  facet_grid(weekend ~ time_of_day)+
  labs(title="Bike share trips per hr by station. Philadelphia, May, 2024")+
  mapTheme


This multi-plot shows the need to balance stations by bringing bikes from stations at the fringe of the network to stations closer to the core, due to the fact that core stations will have more churn.

This focus on trips by stations reflects features not otherwise captured in the model, such as amenities near each station. Given that amenities are fixed in both time and space, inclusion of these amenities would result in mutli-colinearity. Therefore, our models capture the effect of these features using station locations themselves as a proxy rather than including the amenities in the model explicitly.

Create Space-Time Panel

# length(unique(dat_census$interval60)) * length(unique(dat_census$start_station))


study.panel <- 
  expand.grid(interval60=unique(dat_census$interval60), 
              start_station = unique(dat_census$start_station)) %>%
  left_join(., dat_census %>%
              dplyr::select(start_station, start_station, Origin.Tract, from_longitude, from_latitude )%>%
              distinct() %>%
              group_by(start_station) %>%
              slice(1))

# nrow(study.panel)      
ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start_station, Origin.Tract, from_longitude, from_latitude) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(start_station) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE)
ride.panel <- 
  left_join(ride.panel, phillyCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

Create time lags

ride.panel <- 
  ride.panel %>% 
  arrange(start_station, interval60) %>% 
  mutate(lagHour = dplyr::lag(Trip_Count,1),
         lag2Hours = dplyr::lag(Trip_Count,2),
         lag3Hours = dplyr::lag(Trip_Count,3),
         lag4Hours = dplyr::lag(Trip_Count,4),
         lag12Hours = dplyr::lag(Trip_Count,12),
         lag1day = dplyr::lag(Trip_Count,24),
         holiday = ifelse(yday(interval60) == 148,1,0)) %>%
   mutate(day = yday(interval60)) %>%
   mutate(hour = hour(interval60)) %>%
   mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
                                 dplyr::lag(holiday, 2) == 1 ~ "PlustTwoDays",
                                 dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
                                 dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
                                 dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
                                 dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
         holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
    group_by(interval60) %>% 
    summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
    gather(Variable, Value, -interval60, -Trip_Count) %>%
    mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
                                                "lag12Hours","lag1day")))%>%
    group_by(Variable) %>%  
    summarize(correlation = round(cor(Value, Trip_Count),2))

Build Models

ride.Train <- filter(ride.panel, week >= 20)
ride.Test <- filter(ride.panel, week < 20)

ride.Train$dotw <- factor(ride.Train$dotw)
ride.Train$hour <- as.numeric(ride.Train$hour)
reg1 <- 
  lm(Trip_Count ~  hour + dotw + Temperature,  data=ride.Train)

reg2 <- 
  lm(Trip_Count ~  start_station + dotw + Temperature,  data=ride.Train)

reg3 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation, 
     data=ride.Train)

reg4 <- 
  lm(Trip_Count ~  start_station +  hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day, 
     data=ride.Train)

reg5 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday, 
     data=ride.Train)

Predict for test data

ride.Test.weekNest <- 
  ride.Test %>%
  nest(-week) 
model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}
week_predictions <- 
  ride.Test.weekNest %>% 
    mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
           BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
           CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
           DTime_Space_FE_timeLags = map(.x = data, fit = reg4, .f = model_pred),
           ETime_Space_FE_timeLags_holidayLags = map(.x = data, fit = reg5, .f = model_pred)) %>% 
    gather(Regression, Prediction, -data, -week) %>%
    mutate(Observed = map(data, pull, Trip_Count),
           Absolute_Error = map2(Observed, Prediction,  ~ abs(.x - .y)),
           MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
           sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))

Test Models

Examine Error Metrics for Accuracy

week_predictions %>%
  dplyr::select(week, Regression, MAE) %>%
  gather(Variable, MAE, -Regression, -week) %>%
  ggplot(aes(week, MAE)) + 
    geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
    scale_fill_manual(values = palette5) +
    labs(title = "Mean Absolute Errors by model specification and week") +
  plotTheme


This plot shows how model accuracy improves (MAE decreases) with each feature added to the model. Interesting to note here is that the model does not appear to improve when we add temporal proximity to holidays. This may be due to the fact that there aren’t many major holidays in the window of time that we used to train the model.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station)) %>%
    dplyr::select(interval60, start_station, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -start_station) %>%
    group_by(Regression, Variable, interval60) %>%
    summarize(Value = sum(Value)) %>%
    ggplot(aes(interval60, Value, colour=Variable)) + 
      geom_line(size = 1.1) + 
      facet_wrap(~Regression, ncol=1) +
      labs(title = "Predicted/Observed bike share time series", subtitle = "Philadelphia; A test set of 2 weeks",  x = "Hour", y= "Station Trips") +
      plotTheme


This plot shows how each successive feature we added to our model improved how tightly our predictions track with the observed condition.

Space-Time Error Evaluation

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           from_latitude = map(data, pull, from_latitude), 
           from_longitude = map(data, pull, from_longitude)) %>%
    select(interval60, start_station, from_longitude, from_latitude, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags") %>%
  group_by(start_station, from_longitude, from_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = from_longitude, y = from_latitude, color = MAE), 
             fill = "transparent", alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
  xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
  labs(title="Mean Abs Error, Test Set, Model 5")+
  mapTheme


This plot shows higher errors at the core of the network than the fringe. This is occurring in an area of higher density and activity, and is likely the product of these factors.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           from_latitude = map(data, pull, from_latitude), 
           from_longitude = map(data, pull, from_longitude),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, start_station, from_longitude, 
           from_latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  group_by(start_station, weekend, time_of_day, from_longitude, from_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = from_longitude, y = from_latitude, color = MAE), 
             fill = "transparent", size = 0.5, alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
  xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors, Test Set")+
  mapTheme


When we break out the errors by time of day on weekday and weekend, we can similar patterns in distribution to the the overall map of MAE. On the weekends, the higher errors tended to occur further outside of center city, likely due to greater ridership along the Schuylkill River Trail.

control <- trainControl(method="cv", number=20)

# Fit the model
set.seed(123) # for reproducibility
model_cv <- train(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday, 
                  data=ride.panel,
                  method="lm",
                  trControl=control,
                  na.action=na.pass)

# View the results
print(model_cv)
## Linear Regression 
## 
## 178704 samples
##     12 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (20 fold) 
## Summary of sample sizes: 169768, 169769, 169769, 169769, 169769, 169769, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE      
##   1.040202  0.3320501  0.6456757
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE
mae_values <- as.data.frame(model_cv$resample$MAE)
names(mae_values) <- "MAE"
 
mean_mae <- mean(mae_values$MAE)
std_mae <- sd(mae_values$MAE)

ggplot(data = mae_values, aes(x = MAE)) +
  geom_histogram(binwidth = 0.001) +
  geom_vline(xintercept = mean_mae, color = "red", linetype = "dashed", size = 1) +
  geom_vline(xintercept = mean_mae + std_mae, color = "green", linetype = "dashed", size = 1) +
  geom_vline(xintercept = mean_mae - std_mae, color = "green", linetype = "dashed", size = 1)


Here is a histogram of mean absolute error, with vertical lines for mean (red) and standard deviation (green). A tighter histogram here would represent greater generalizability of the model. The spread of this histogram indicates decent generalizability, but it could be better.

Conclusion

Interpreting our predictions

Conclude with how useful your algorithm is for the bike re-balancing plan.


Our model is not great a capturing peak times, and it isn’t particularly generalizable. However, it does capture the spatial and temporal dimensions of bikeshare demand, which are a good starting point for bike balancing, assuming that the model continues to be improved over time. Some ways that the model could be improved are through engineering of temporally-variable amenities in the form of businesses, museums, and restaurants encoded spatially but also with the hours they are open and closed.

Our data also looks at only May to June. Given the high number of higher education institutions in Philly, it is important to consider how the dip in the city’s population will be reflected in ridership, particularly in areas of the city with higher proportions of student housing and university buildings. Engineering features to reflect this fluctuation would likely improve model accuracy. It is also important to consider how bikeshare demand changes across the seasons. Therefore, before this model is used to inform bike re-balancing beyond May-June, it should be trained on data gathered across an entire year.

LS0tCnRpdGxlOiAiSG9tZXdvcmsgNjogU3BhY2UtVGltZSBQcmVkaWN0aW9uIG9mIEJpa2UgU2hhcmUgRGVtYW5kIgphdXRob3I6ICJPbGl2ZXIgQXR3b29kIgpkYXRlOiAiTm92ZW1iZXIgMjEsIDIwMjMiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCiMgSW50cm9kdWN0aW9uCkJpa2VzaGFyZSBzeXN0ZW1zIGFyZSBhbiBleGNlbGxlbnQsIGxvdy1jYXJib24gYXBwcm9hY2ggdG8gbW92aW5nIHBlb3BsZSBmcm9tIHBvaW50IEEgdG8gQiBpbiBjaXRpZXMuIEhvd2V2ZXIsIHRoZXkgaGF2ZSB1bmlxdWUgY2hhbGxlbmdlcyByZWxhdGVkIHRvIGZsdWN0dWF0aW9ucyBpbiBzdXBwbHkgYW5kIGRlbWFuZCBvZiBiaWtlcyBvdmVyIHRpbWUgYW5kIGFjcm9zcyBzcGFjZS4gQXQgdGhlIGNvcmUgb2YgYW55IHN1Y2Nlc3NmdWwgYmlrZXNoYXJlIHByb2dyYW0gaXMgYSBiYWxhbmNlZCBpbnZlbnRvcnksIHdoZXJlIGFsbCBkb2NrcyBoYXZlIGJpa2VzIHRvIHJpZGUgYW5kIHNwb3RzIHRvIGRvY2sgYXZhaWxhYmxlLiBBbnRpY2lwYXRpbmcgdXNlciBkZW1hbmQgaW4gdGhlc2Ugc3lzdGVtcyBhbGxvd3MgYWRtaW5pc3RyYXRvcnMgdG8gbW92ZSBiaWtlcyBhaGVhZCBvZiB0aGF0IGRlbWFuZC4KPGJyPgo8YnI+CkluIG9yZGVyIHRvIGVmZmVjdGl2ZWx5IGJhbGFuY2UgdGhlIHN5c3RlbSwgc3lzdGVtcyBhZG1pbmlzdHJhdG9ycyBzaG91bGQgZm9jdXMgb24gbW92aW5nIGJpa2VzIG9uIGVpdGhlciBzaWRlIG9mIHRoZSB0aGUgcnVzaCBob3VyIHBlYWtzIGFuZCBicmluZ2luZyBiaWtlcyBmcm9tIGRvY2tzIGF0IGZyaW5nZSBzdGF0aW9ucyB0byBjb3JlIHN0YXRpb25zLiBPbmUgd2F5IHRvIGRvIHRoaXMgd291bGQgYmUgdG8gJ2dhbWlmeScgYmFsYW5jaW5nIGZvciByaWRlcnMgdGhyb3VnaCBpbmNlbnRpdml6aW5nIHJpZGVycyB0byBicmluZyBiaWtlcyBiYWNrIHRvIHRoZWlyIG9yaWdpbiBzdGF0aW9ucyBhdCB0aGUgZW5kIG9mIHRoZSBkYXkgYW5kL29yIGluY2VudGl2aXppbmcgdXNlcnMgdG8gbW92ZSBiaWtlcyBmcm9tIGZ1bGwgc3RhdGlvbnMgdG8gZW1wdHkgc3RhdGlvbnMgZHVyaW5nIG9mZi1wZWFrIGhvdXJzIHRvIHJlZHVjZSBuZWVkIG9mIHRydWNrcyBicmluZ2luZyBiaWtlcyB0byBhbmQgZnJvLiBUbyBtYW5hZ2UgdGhlIHJlbWFpbmRlciBvZiB0aGUgcmUtYmFsYW5jaW5nIG5lZWQsIGJpa2VzaGFyZSBhZG1pbmlzdHJhdG9ycyBzaG91bGQgZGlyZWN0IGEgc21hbGwgY3JldyBydW5uaW5nIGEgZmxlZXQgb2YgdHJ1Y2tzIHRvIGNvbGxlY3QgYmlrZXMgYW5kIHJlYmFsYW5jZSB0aGVtIGFjY29yZGluZyB0byBwcm9qZWN0ZWQgZnV0dXJlIGRlbWFuZC4gUHJlZGljdGluZyBkZW1hbmQgdHdvIHdlZWtzIGludG8gdGhlIGZ1dHVyZSB3b3VsZCBiYWxhbmNlIHRoZSBjb21wZXRpbmcgbmVlZHMgb2YgYWNjdXJhY3kgYW5kIHByb2FjdGl2ZSBzY2hlZHVsaW5nIG9mIGVtcGxveWVlcy4KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpybShsaXN0PWxzKCkpCgprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyBTZXR1cAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh0aWdyaXMpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHJpZW0pCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeShnZ2FuaW1hdGUpCmxpYnJhcnkoY2FyZXQpCgpzZWxlY3QgPC0gZHBseXI6OnNlbGVjdApmaWx0ZXIgPC0gZHBseXI6OmZpbHRlcgoKcGxvdFRoZW1lIDwtIHRoZW1lKAogIHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwKICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAjIFNldCB0aGUgZW50aXJlIGNoYXJ0IHJlZ2lvbiB0byBibGFuawogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLAogIHBsb3QuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgI3BhbmVsLmJvcmRlcj1lbGVtZW50X3JlY3QoY29sb3VyPSIjRjBGMEYwIiksCiAgIyBGb3JtYXQgdGhlIGdyaWQKICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXI9IiNEMEQwRDAiLHNpemU9LjIpLAogIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpKQoKbWFwVGhlbWUgPC0gdGhlbWUocGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksCiAgICAgICAgICAgICAgICAgIGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJvcmRlcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91ciA9ICd0cmFuc3BhcmVudCcpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsIAogICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxLCAxLCAxLCAxLCAnY20nKSwKICAgICAgICAgICAgICAgICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDEsICJjbSIpLCBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjIsICJjbSIpKQoKcGFsZXR0ZTUgPC0gYygiI2VmZjNmZiIsIiNiZGQ3ZTciLCIjNmJhZWQ2IiwiIzMxODJiZCIsIiMwODUxOWMiKQpwYWxldHRlNCA8LSBjKCIjRDJGQkQ0IiwiIzkyQkNBQiIsIiM1MjdEODIiLCIjMTIzRjVBIikKcGFsZXR0ZTIgPC0gYygiIzZiYWVkNiIsIiMwODUxOWMiKQoKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29saXZlZ2FyZGVuZXIvbXVzYV81MDgwXzIwMjMvbWFpbi9XZWVrXzgvUGhpbGx5JTIwQmlrZSUyMFNoYXJlL2RhdGEvIgoKIyBJbnN0YWxsIENlbnN1cyBBUEkgS2V5CnRpZHljZW5zdXM6OmNlbnN1c19hcGlfa2V5KCJhM2ViZGYxNjQ4YjdmYjIxZGY1NWRmNzI0NmQ5NjQyZjA0MGMwZWUwIiwgb3ZlcndyaXRlID0gVFJVRSkKYGBgCgojIERhdGEKCmBgYHtyIHJlYWRfZGF0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnfQpkYXQgPC0gc3RfcmVhZCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29saXZlZ2FyZGVuZXIvbXVzYV81MDgwXzIwMjMvbWFpbi9XZWVrXzgvUGhpbGx5JTIwQmlrZSUyMFNoYXJlL2RhdGEvaW5kZWdvLXRyaXBzLTIwMjMtcTIuY3N2IikKCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CiMgQ29udmVydCAnc3RhcnRfdGltZScgdG8gUE9TSVhjdCBkYXRlLXRpbWUgZm9ybWF0CmRhdCRzdGFydF90aW1lIDwtIG1keV9obShkYXQkc3RhcnRfdGltZSkKCiMgRGVmaW5lIHRoZSBzdGFydCBhbmQgZW5kIGRhdGVzCnN0YXJ0X2RhdGUgPC0gYXMuRGF0ZSgiMjAyMy0wNS0wMSIpCmVuZF9kYXRlIDwtIGFzLkRhdGUoIjIwMjMtMDYtMDQiKQoKIyBGaWx0ZXIgdGhlIHJvd3MgYmV0d2VlbiB0aGUgc3RhcnQgYW5kIGVuZCBkYXRlcwpkYXQgPC0gZGF0ICU+JQogIGZpbHRlcihzdGFydF90aW1lID49IHN0YXJ0X2RhdGUgJiBzdGFydF90aW1lIDw9IGVuZF9kYXRlKQoKCmBgYAoKYGBge3IgdGltZV9iaW5zfQpkYXQyIDwtIGRhdCAlPiUKICBtdXRhdGUoaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoc3RhcnRfdGltZSwgdW5pdCA9ICJob3VyIiksCiAgICAgICAgIGludGVydmFsMTUgPSBmbG9vcl9kYXRlKHN0YXJ0X3RpbWUsIHVuaXQgPSAiMTUgbWlucyIpLAogICAgICAgICB3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWw9VFJVRSkpICU+JSAKICAgICAgICAgZmlsdGVyKGVuZF9sb24gIT0gIiIsIGVuZF9sYXQgIT0gIiIsCiAgICAgICAgIHN0YXJ0X2xvbiAhPSAiIiwgc3RhcnRfbGF0ICE9ICIiKQoKYGBgCgoKIyMgSW1wb3J0IENlbnN1cyBJbmZvCgpgYGB7ciBnZXRfY2Vuc3VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQpwaGlsbHlDZW5zdXMgPC0gCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIAogICAgICAgICAgeWVhciA9IDIwMjEsIAogICAgICAgICAgc3RhdGUgPSAiUEEiLCAKICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgCiAgICAgICAgICBjb3VudHk9YygiUGhpbGFkZWxwaGlhIiksCiAgICAgICAgICBvdXRwdXQgPSAid2lkZSIpICU+JQogIHJlbmFtZShUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsCiAgICAgICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwKICAgICAgICAgTWVkX0FnZSA9IEIwMTAwMl8wMDFFLAogICAgICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwKICAgICAgICAgVHJhdmVsX1RpbWUgPSBCMDgwMTNfMDAxRSwKICAgICAgICAgTnVtX0NvbW11dGVycyA9IEIwODAxMl8wMDFFLAogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwKICAgICAgICAgVG90YWxfUHVibGljX1RyYW5zID0gQjA4MzAxXzAxMEUpICU+JQogIGRwbHlyOjpzZWxlY3QoVG90YWxfUG9wLCBNZWRfSW5jLCBXaGl0ZV9Qb3AsIFRyYXZlbF9UaW1lLAogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQsIFRvdGFsX1B1YmxpY19UcmFucywKICAgICAgICAgTWVkX0FnZSwKICAgICAgICAgR0VPSUQsIGdlb21ldHJ5KSAlPiUKICBtdXRhdGUoUGVyY2VudF9XaGl0ZSA9IFdoaXRlX1BvcCAvIFRvdGFsX1BvcCwKICAgICAgICAgTWVhbl9Db21tdXRlX1RpbWUgPSBUcmF2ZWxfVGltZSAvIFRvdGFsX1B1YmxpY19UcmFucywKICAgICAgICAgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zID0gVG90YWxfUHVibGljX1RyYW5zIC8gTWVhbnNfb2ZfVHJhbnNwb3J0KQoKCmBgYAoKYGBge3IgZXh0cmFjdF9nZW9tZXRyaWVzIH0KcGhpbGx5VHJhY3RzIDwtIAogIHBoaWxseUNlbnN1cyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIGRwbHlyOjpzZWxlY3QoR0VPSUQsIGdlb21ldHJ5KSAlPiUgCiAgc3Rfc2YKCmBgYAoKYGBge3IgYWRkX2NlbnN1c190cmFjdHMgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZGF0X2NlbnN1cyA8LSBzdF9qb2luKGRhdDIgJT4lIAogICAgICAgICAgZmlsdGVyKGlzLm5hKHN0YXJ0X2xvbikgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnRfbGF0KSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICBpcy5uYShlbmRfbGF0KSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICBpcy5uYShlbmRfbG9uKSA9PSBGQUxTRSkgJT4lCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sb24iLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpLAogICAgICAgIHBoaWxseVRyYWN0cyAlPiUKICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9NDMyNiksCiAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLAogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQogIG11dGF0ZShmcm9tX2xvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZHBseXI6OnNlbGVjdCgtZ2VvbWV0cnkpJT4lCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygiZW5kX2xvbiIsICJlbmRfbGF0IiksIGNycyA9IDQzMjYpICU+JQogIHN0X2pvaW4oLiwgcGhpbGx5VHJhY3RzICU+JQogICAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLAogICAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLAogICAgICAgICAgbGVmdCA9IFRSVUUpICU+JQogIHJlbmFtZShEZXN0aW5hdGlvbi5UcmFjdCA9IEdFT0lEKSAgJT4lCiAgbXV0YXRlKGVuZF9sb24gPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAxKSksCiAgICAgICAgIGVuZF9sYXQgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5KQpgYGAKIyBEZXNjcmliZSBhbmQgRXhwbG9yZSB0aGUgRGF0YQoKIyMgSW1wb3J0IFdlYXRoZXIgRGF0YQoKYGBge3IgaW1wb3J0X3dlYXRoZXIsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFIH0Kd2VhdGhlci5QYW5lbCA8LSAKICByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiUEhMIiwgZGF0ZV9zdGFydCA9ICIyMDIzLTA1LTAxIiwgZGF0ZV9lbmQgPSAiMjAyMy0wNi0wNCIpICU+JQogIGRwbHlyOjpzZWxlY3QodmFsaWQsIHRtcGYsIHAwMWksIHNrbnQpJT4lCiAgcmVwbGFjZShpcy5uYSguKSwgMCkgJT4lCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IHltZF9oKHN1YnN0cih2YWxpZCwxLDEzKSkpICU+JQogICAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsPVRSVUUpKSAlPiUKICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogICAgc3VtbWFyaXplKFRlbXBlcmF0dXJlID0gbWF4KHRtcGYpLAogICAgICAgICAgICAgIFByZWNpcGl0YXRpb24gPSBzdW0ocDAxaSksCiAgICAgICAgICAgICAgV2luZF9TcGVlZCA9IG1heChza250KSkgJT4lCiAgICBtdXRhdGUoVGVtcGVyYXR1cmUgPSBpZmVsc2UoVGVtcGVyYXR1cmUgPT0gMCwgNDIsIFRlbXBlcmF0dXJlKSkKCmBgYAoKYGBge3IgcGxvdF93ZWF0aGVyLCBjYXRjaGUgPSBUUlVFfQpncmlkLmFycmFuZ2UoCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFByZWNpcGl0YXRpb24pKSArIGdlb21fbGluZSgpICsgCiAgbGFicyh0aXRsZT0iUGVyY2lwaXRhdGlvbiIsIHg9IkhvdXIiLCB5PSJQcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUsCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJXaW5kIFNwZWVkIiwgeD0iSG91ciIsIHk9IldpbmQgU3BlZWQiKSArIHBsb3RUaGVtZSwKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJUZW1wZXJhdHVyZSIsIHg9IkhvdXIiLCB5PSJUZW1wZXJhdHVyZSIpICsgcGxvdFRoZW1lLAogIHRvcD0iV2VhdGhlciBEYXRhIC0gUGhpbGFkZWxwaGlhIFBITCAtIE1heS1KdW5lLCAyMDIzIikKYGBgCjxicj4KSGVyZSBpcyB0aGUgd2VhdGhlciBkYXRhLCBzaG93aW5nIGhvdyB0ZW1wZXJhdHVyZSBhbmQgd2luZCBzcGVlZCBhcmUgc29tZXdoYXQgdGllZCB0b2dldGhlciBhbmQgZmx1Y3R1YXRlIHdpdGggdGhlIGRheS1uaWdodCBzd2luZy4KCmBgYHtyIHRyaXBfdGltZXNlcmllcyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgICAgICAgdGFsbHkoKSkrCiAgZ2VvbV9saW5lKGFlcyh4ID0gaW50ZXJ2YWw2MCwgeSA9IG4pKSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBoci4gUGhpbGFkZWxwaGlhLCBNYXktSnVuZSwgMjAyMyIsCiAgICAgICB4PSJEYXRlIiwgCiAgICAgICB5PSJOdW1iZXIgb2YgdHJpcHMiKSsKICBwbG90VGhlbWUKYGBgCjxicj4KVGhpcyBwbG90IHNob3dzIGEgcmVsYXRpdmVseSBjb25zaXN0ZW50IGRhaWx5IGZsdWN0dWF0aW9uLCB3aXRoIHR3byBwZWFrcyBvbiBlYWNoIGRheSBvZiB0aGUgd29yayB3ZWVrIC0gb25lIGluIHRoZSBtb3JuaW5nIGFuZCBhIGxhcmdlciBvbmUgaW4gdGhlIGV2ZW5pbmcgcnVzaCBob3VyLgoKQWxzbyB2aXNpYmxlIGhlcmUgaXMgdGhlIHJlbGF0aXZlbHkgbG93ZXIgcmlkZXJzaGlwIG9uIHdlZWtlbmRzLiBQYXJ0aWN1bGFybHkgbm90YWJsZSBvbiBNYXkgMTMgYW5kIDIwLgpgYGB7ciBtZWFuX3RyaXBzX2hpc3QsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0KZGF0X2NlbnN1cyAlPiUKICAgICAgICBtdXRhdGUodGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHRpbWVfb2ZfZGF5KSAlPiUKICAgICAgICAgdGFsbHkoKSU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIHRpbWVfb2ZfZGF5KSU+JQogIHN1bW1hcml6ZShtZWFuX3RyaXBzID0gbWVhbihuKSklPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcyksIGJpbndpZHRoID0gMSkrCiAgbGFicyh0aXRsZT0iTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uLiBQaGlsYWRlbHBoaWEsIE1heSwgMjAyMyIsCiAgICAgICB4PSJOdW1iZXIgb2YgdHJpcHMiLCAKICAgICAgIHk9IkZyZXF1ZW5jeSIpKwogIGZhY2V0X3dyYXAofnRpbWVfb2ZfZGF5KSsKICBwbG90VGhlbWUKYGBgCjxicj4KTWlkLWRheSBhbmQgUE0gcnVzaCBoYXZlIG1vcmUgbm9ybWFsIGRpc3RyaWJ1dGlvbnMsIHdoZXJlYXMgQU0gcnVzaCBhbmQgT3Zlcm5pZ2h0IGFyZSBza2V3ZWQgdG93YXJkcyBtb3N0bHkgb25lIG9yIHR3byB0cmlwcy4KCmBgYHtyIHRyaXBzX3N0YXRpb25fZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uKSAlPiUKICAgICAgICAgdGFsbHkoKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDEpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIFBoaWxhZGVscGhpYSwgTWF5LCAyMDIzIiwKICAgICAgIHg9IlRyaXAgQ291bnRzIiwgCiAgICAgICB5PSJOdW1iZXIgb2YgU3RhdGlvbnMiKSsKICBwbG90VGhlbWUKYGBgCjxicj4KT3ZlcmFsbCwgdGhlIG1vc3QgZnJlcXVlbnQgY291bnQgaXMgYSBzaW5nbGUgdHJpcC4KCmBgYHtyIHRyaXBzX2hvdXJfZG90dyB9CgpkYXRfY2Vuc3VzIDwtIGRhdF9jZW5zdXMgJT4lCiAgbXV0YXRlKHN0YXJ0X3RpbWUgPSBhcy5QT1NJWGN0KHN0YXJ0X3RpbWUsIGZvcm1hdCA9ICIlbS8lZC8lWSAlSDolTSIpKQoKIyBnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0X3RpbWUpKSkrCiMgICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDEpKwojICAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBpbiBQaGlsYWRlbHBoaWEsIGJ5IGRheSBvZiB0aGUgd2VlaywgTWF5LCAyMDE4IiwKIyAgICAgICAgeD0iSG91ciIsIAojICAgICAgICB5PSJUcmlwIENvdW50cyIpKwojICAgICAgcGxvdFRoZW1lCgoKZ2dwbG90KGRhdF9jZW5zdXMgJT4lIAogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRfdGltZSksCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSkpKwogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gd2Vla2VuZCksIGJpbndpZHRoID0gMSkrCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBpbiBQaGlsYWRlbHBoaWEgLSB3ZWVrZW5kIHZzIHdlZWtkYXksIE1heSwgMjAxOCIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIpKwogICAgIHBsb3RUaGVtZQpgYGAKPGJyPgpGcm9tIHRoaXMgZ3JhcGggd2UgY2FuIHNlZSBvbmUgcGVhayBwZXIgZGF5IG9uIHdlZWtlbmRzIHZlcnN1cyB0d28gb24gd2Vla2RheXMuIE9uIGF2ZXJhZ2UsIHJpZGVyc2hpcCBpcyBsb3dlciBvbiB3ZWVrZW5kcy4gT25lIGtleSB0YWtlYXdheSBmcm9tIHRoaXMgZ3JhcGggaXMgdGhlIGdyZWF0ZXIgbmVlZCBmb3IgYmFsYW5jaW5nIHRoZSBzdXBwbHkgb2YgYmlrZXMgb24gd2Vla2RheXMgdGhhbiBvbiB3ZWVrZW5kcy4KCmBgYHtyIG9yaWdpbl9tYXAgfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5VHJhY3RzICU+JQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkrCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUgCiAgICAgICAgICAgIG11dGF0ZShob3VyID0gaG91cihzdGFydF90aW1lKSwKICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLAogICAgICAgICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgZnJvbV9sYXRpdHVkZSwgZnJvbV9sb25naXR1ZGUsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5KSAlPiUKICAgICAgICAgICAgICB0YWxseSgpLAogICAgICAgICAgICBhZXMoeD1mcm9tX2xvbmdpdHVkZSwgeSA9IGZyb21fbGF0aXR1ZGUsIGNvbG9yID0gbiksIAogICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjQsIHNpemUgPSAwLjMpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLAogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBQaGlsYWRlbHBoaWEsIE1heSwgMjAyNCIpKwogIG1hcFRoZW1lCgpgYGAKPGJyPgpUaGlzIG11bHRpLXBsb3Qgc2hvd3MgdGhlIG5lZWQgdG8gYmFsYW5jZSBzdGF0aW9ucyBieSBicmluZ2luZyBiaWtlcyBmcm9tIHN0YXRpb25zIGF0IHRoZSBmcmluZ2Ugb2YgdGhlIG5ldHdvcmsgdG8gc3RhdGlvbnMgY2xvc2VyIHRvIHRoZSBjb3JlLCBkdWUgdG8gdGhlIGZhY3QgdGhhdCBjb3JlIHN0YXRpb25zIHdpbGwgaGF2ZSBtb3JlIGNodXJuLgoKVGhpcyBmb2N1cyBvbiB0cmlwcyBieSBzdGF0aW9ucyByZWZsZWN0cyBmZWF0dXJlcyBub3Qgb3RoZXJ3aXNlIGNhcHR1cmVkIGluIHRoZSBtb2RlbCwgc3VjaCBhcyBhbWVuaXRpZXMgbmVhciBlYWNoIHN0YXRpb24uIEdpdmVuIHRoYXQgYW1lbml0aWVzIGFyZSBmaXhlZCBpbiBib3RoIHRpbWUgYW5kIHNwYWNlLCBpbmNsdXNpb24gb2YgdGhlc2UgYW1lbml0aWVzIHdvdWxkIHJlc3VsdCBpbiBtdXRsaS1jb2xpbmVhcml0eS4gVGhlcmVmb3JlLCBvdXIgbW9kZWxzIGNhcHR1cmUgdGhlIGVmZmVjdCBvZiB0aGVzZSBmZWF0dXJlcyB1c2luZyBzdGF0aW9uIGxvY2F0aW9ucyB0aGVtc2VsdmVzIGFzIGEgcHJveHkgcmF0aGVyIHRoYW4gaW5jbHVkaW5nIHRoZSBhbWVuaXRpZXMgaW4gdGhlIG1vZGVsIGV4cGxpY2l0bHkuCgojIyBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbAoKYGBge3IgcGFuZWxfbGVuZ3RoX2NoZWNrICwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiMgbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApKSAqIGxlbmd0aCh1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkKCgpzdHVkeS5wYW5lbCA8LSAKICBleHBhbmQuZ3JpZChpbnRlcnZhbDYwPXVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApLCAKICAgICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gdW5pcXVlKGRhdF9jZW5zdXMkc3RhcnRfc3RhdGlvbikpICU+JQogIGxlZnRfam9pbiguLCBkYXRfY2Vuc3VzICU+JQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfc3RhdGlvbiwgT3JpZ2luLlRyYWN0LCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSApJT4lCiAgICAgICAgICAgICAgZGlzdGluY3QoKSAlPiUKICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uKSAlPiUKICAgICAgICAgICAgICBzbGljZSgxKSkKCiMgbnJvdyhzdHVkeS5wYW5lbCkgICAgICAKYGBgCgpgYGB7ciBjcmVhdGVfcGFuZWwgLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUucGFuZWwgPC0gCiAgZGF0X2NlbnN1cyAlPiUKICBtdXRhdGUoVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgcmlnaHRfam9pbihzdHVkeS5wYW5lbCkgJT4lIAogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIE9yaWdpbi5UcmFjdCwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JQogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKGlzLm5hKHN0YXJ0X3N0YXRpb24pID09IEZBTFNFKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSkpICU+JQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFKQpgYGAKCmBgYHtyIGNlbnN1c19hbmRfcGFuZWwgLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUucGFuZWwgPC0gCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIHBoaWxseUNlbnN1cyAlPiUKICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KC1nZW9tZXRyeSksIGJ5ID0gYygiT3JpZ2luLlRyYWN0IiA9ICJHRU9JRCIpKQpgYGAKCiMjIENyZWF0ZSB0aW1lIGxhZ3MKCmBgYHtyIHRpbWVfbGFncyAsIG1lc3NhZ2UgPSBGQUxTRX0KcmlkZS5wYW5lbCA8LSAKICByaWRlLnBhbmVsICU+JSAKICBhcnJhbmdlKHN0YXJ0X3N0YXRpb24sIGludGVydmFsNjApICU+JSAKICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwKICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLAogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwKICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLAogICAgICAgICBob2xpZGF5ID0gaWZlbHNlKHlkYXkoaW50ZXJ2YWw2MCkgPT0gMTQ4LDEsMCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lCiAgIG11dGF0ZShob3VyID0gaG91cihpbnRlcnZhbDYwKSkgJT4lCiAgIG11dGF0ZShob2xpZGF5TGFnID0gY2FzZV93aGVuKGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAyKSA9PSAxIH4gIlBsdXN0VHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDEpID09IDEgfiAiTWludXNPbmVEYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwKICAgICAgICAgaG9saWRheUxhZyA9IGlmZWxzZShpcy5uYShob2xpZGF5TGFnKSA9PSBUUlVFLCAwLCBob2xpZGF5TGFnKSkKCgoKYGBgCgpgYGB7ciBldmFsdWF0ZV9sYWdzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHM9J2hpZGUnfQphcy5kYXRhLmZyYW1lKHJpZGUucGFuZWwpICU+JQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogICAgc3VtbWFyaXNlX2F0KHZhcnMoc3RhcnRzX3dpdGgoImxhZyIpLCAiVHJpcF9Db3VudCIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUKICAgIG11dGF0ZShWYXJpYWJsZSA9IGZhY3RvcihWYXJpYWJsZSwgbGV2ZWxzPWMoImxhZ0hvdXIiLCJsYWcySG91cnMiLCJsYWczSG91cnMiLCJsYWc0SG91cnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGFnMTJIb3VycyIsImxhZzFkYXkiKSkpJT4lCiAgICBncm91cF9ieShWYXJpYWJsZSkgJT4lICAKICAgIHN1bW1hcml6ZShjb3JyZWxhdGlvbiA9IHJvdW5kKGNvcihWYWx1ZSwgVHJpcF9Db3VudCksMikpCmBgYAoKIyBCdWlsZCBNb2RlbHMKCmBgYHtyIHRyYWluX3Rlc3QgfQpyaWRlLlRyYWluIDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49IDIwKQpyaWRlLlRlc3QgPC0gZmlsdGVyKHJpZGUucGFuZWwsIHdlZWsgPCAyMCkKCnJpZGUuVHJhaW4kZG90dyA8LSBmYWN0b3IocmlkZS5UcmFpbiRkb3R3KQpyaWRlLlRyYWluJGhvdXIgPC0gYXMubnVtZXJpYyhyaWRlLlRyYWluJGhvdXIpCmBgYAoKCmBgYHtyIGZpdmVfbW9kZWxzIH0KcmVnMSA8LSAKICBsbShUcmlwX0NvdW50IH4gIGhvdXIgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pCgpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikKCnJlZzMgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24sIAogICAgIGRhdGE9cmlkZS5UcmFpbikKCnJlZzQgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uICsgIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArIGxhZzEySG91cnMgKyBsYWcxZGF5LCAKICAgICBkYXRhPXJpZGUuVHJhaW4pCgpyZWc1IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArbGFnMTJIb3VycyArIGxhZzFkYXkgKyBob2xpZGF5TGFnICsgaG9saWRheSwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQpgYGAKCiMjIFByZWRpY3QgZm9yIHRlc3QgZGF0YQoKYGBge3IgbmVzdF9kYXRhICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUuVGVzdC53ZWVrTmVzdCA8LSAKICByaWRlLlRlc3QgJT4lCiAgbmVzdCgtd2VlaykgCmBgYAoKCmBgYHtyIHByZWRpY3RfZnVuY3Rpb24gfQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsKICAgcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9CmBgYAoKYGBge3IgZG9fcHJlZGljaXRvbnMgfQp3ZWVrX3ByZWRpY3Rpb25zIDwtIAogIHJpZGUuVGVzdC53ZWVrTmVzdCAlPiUgCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzUsIC5mID0gbW9kZWxfcHJlZCkpICU+JSAKICAgIGdhdGhlcihSZWdyZXNzaW9uLCBQcmVkaWN0aW9uLCAtZGF0YSwgLXdlZWspICU+JQogICAgbXV0YXRlKE9ic2VydmVkID0gbWFwKGRhdGEsIHB1bGwsIFRyaXBfQ291bnQpLAogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwKICAgICAgICAgICBNQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBtZWFuLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgIHNkX0FFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgc2QsIG5hLnJtID0gVFJVRSkpCgpgYGAKCiMgVGVzdCBNb2RlbHMKIyMgRXhhbWluZSBFcnJvciBNZXRyaWNzIGZvciBBY2N1cmFjeQoKYGBge3IgcGxvdF9lcnJvcnNfYnlfbW9kZWwgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JQogIGRwbHlyOjpzZWxlY3Qod2VlaywgUmVncmVzc2lvbiwgTUFFKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIE1BRSwgLVJlZ3Jlc3Npb24sIC13ZWVrKSAlPiUKICBnZ3Bsb3QoYWVzKHdlZWssIE1BRSkpICsgCiAgICBnZW9tX2JhcihhZXMoZmlsbCA9IFJlZ3Jlc3Npb24pLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsKICAgIGxhYnModGl0bGUgPSAiTWVhbiBBYnNvbHV0ZSBFcnJvcnMgYnkgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgd2VlayIpICsKICBwbG90VGhlbWUKYGBgCjxicj4KVGhpcyBwbG90IHNob3dzIGhvdyBtb2RlbCBhY2N1cmFjeSBpbXByb3ZlcyAoTUFFIGRlY3JlYXNlcykgd2l0aCBlYWNoIGZlYXR1cmUgYWRkZWQgdG8gdGhlIG1vZGVsLiBJbnRlcmVzdGluZyB0byBub3RlIGhlcmUgaXMgdGhhdCB0aGUgbW9kZWwgZG9lcyBub3QgYXBwZWFyIHRvIGltcHJvdmUgd2hlbiB3ZSBhZGQgdGVtcG9yYWwgcHJveGltaXR5IHRvIGhvbGlkYXlzLiBUaGlzIG1heSBiZSBkdWUgdG8gdGhlIGZhY3QgdGhhdCB0aGVyZSBhcmVuJ3QgbWFueSBtYWpvciBob2xpZGF5cyBpbiB0aGUgd2luZG93IG9mIHRpbWUgdGhhdCB3ZSB1c2VkIHRvIHRyYWluIHRoZSBtb2RlbC4KCmBgYHtyIGVycm9yX3ZzX2FjdHVhbF90aW1lc2VyaWVzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uKSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUKICAgIHVubmVzdCgpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLVJlZ3Jlc3Npb24sIC1pbnRlcnZhbDYwLCAtc3RhcnRfc3RhdGlvbikgJT4lCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBWYXJpYWJsZSwgaW50ZXJ2YWw2MCkgJT4lCiAgICBzdW1tYXJpemUoVmFsdWUgPSBzdW0oVmFsdWUpKSAlPiUKICAgIGdncGxvdChhZXMoaW50ZXJ2YWw2MCwgVmFsdWUsIGNvbG91cj1WYXJpYWJsZSkpICsgCiAgICAgIGdlb21fbGluZShzaXplID0gMS4xKSArIAogICAgICBmYWNldF93cmFwKH5SZWdyZXNzaW9uLCBuY29sPTEpICsKICAgICAgbGFicyh0aXRsZSA9ICJQcmVkaWN0ZWQvT2JzZXJ2ZWQgYmlrZSBzaGFyZSB0aW1lIHNlcmllcyIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYTsgQSB0ZXN0IHNldCBvZiAyIHdlZWtzIiwgIHggPSAiSG91ciIsIHk9ICJTdGF0aW9uIFRyaXBzIikgKwogICAgICBwbG90VGhlbWUKYGBgCjxicj4KVGhpcyBwbG90IHNob3dzIGhvdyBlYWNoIHN1Y2Nlc3NpdmUgZmVhdHVyZSB3ZSBhZGRlZCB0byBvdXIgbW9kZWwgaW1wcm92ZWQgaG93IHRpZ2h0bHkgb3VyIHByZWRpY3Rpb25zIHRyYWNrIHdpdGggdGhlIG9ic2VydmVkIGNvbmRpdGlvbi4KCiMjIFNwYWNlLVRpbWUgRXJyb3IgRXZhbHVhdGlvbgoKYGBge3IgZXJyb3JzX2J5X3N0YXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb24pLCAKICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCAKICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSkpICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQpnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Q2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIGdlb21fcG9pbnQoYWVzKHggPSBmcm9tX2xvbmdpdHVkZSwgeSA9IGZyb21fbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjQpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLAogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKwogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNSIpKwogIG1hcFRoZW1lCmBgYAo8YnI+ClRoaXMgcGxvdCBzaG93cyBoaWdoZXIgZXJyb3JzIGF0IHRoZSBjb3JlIG9mIHRoZSBuZXR3b3JrIHRoYW4gdGhlIGZyaW5nZS4gVGhpcyBpcyBvY2N1cnJpbmcgaW4gYW4gYXJlYSBvZiBoaWdoZXIgZGVuc2l0eSBhbmQgYWN0aXZpdHksIGFuZCBpcyBsaWtlbHkgdGhlIHByb2R1Y3Qgb2YgdGhlc2UgZmFjdG9ycy4KCmBgYHtyIHN0YXRpb25fc3VtbWFyeSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIAogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgZnJvbV9sb25naXR1ZGUsIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKGFicyhPYnNlcnZlZC1QcmVkaWN0aW9uKSwgbmEucm0gPSBUUlVFKSklPiUKICBnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Q2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIGdlb21fcG9pbnQoYWVzKHggPSBmcm9tX2xvbmdpdHVkZSwgeSA9IGZyb21fbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjQpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLAogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKwogIGZhY2V0X2dyaWQod2Vla2VuZH50aW1lX29mX2RheSkrCiAgbGFicyh0aXRsZT0iTWVhbiBBYnNvbHV0ZSBFcnJvcnMsIFRlc3QgU2V0IikrCiAgbWFwVGhlbWUKICAKYGBgCjxicj4KV2hlbiB3ZSBicmVhayBvdXQgdGhlIGVycm9ycyBieSB0aW1lIG9mIGRheSBvbiB3ZWVrZGF5IGFuZCB3ZWVrZW5kLCB3ZSBjYW4gc2ltaWxhciBwYXR0ZXJucyBpbiBkaXN0cmlidXRpb24gdG8gdGhlIHRoZSBvdmVyYWxsIG1hcCBvZiBNQUUuIE9uIHRoZSB3ZWVrZW5kcywgdGhlIGhpZ2hlciBlcnJvcnMgdGVuZGVkIHRvIG9jY3VyIGZ1cnRoZXIgb3V0c2lkZSBvZiBjZW50ZXIgY2l0eSwgbGlrZWx5IGR1ZSB0byBncmVhdGVyIHJpZGVyc2hpcCBhbG9uZyB0aGUgU2NodXlsa2lsbCBSaXZlciBUcmFpbC4KCmBgYHtyIGN2fQpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTIwKQoKIyBGaXQgdGhlIG1vZGVsCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cm1vZGVsX2N2IDwtIHRyYWluKFRyaXBfQ291bnQgfiBzdGFydF9zdGF0aW9uICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24gKwogICAgICAgICAgICAgICAgICAgbGFnSG91ciArIGxhZzJIb3VycyArbGFnM0hvdXJzICtsYWcxMkhvdXJzICsgbGFnMWRheSArIGhvbGlkYXlMYWcgKyBob2xpZGF5LCAKICAgICAgICAgICAgICAgICAgZGF0YT1yaWRlLnBhbmVsLAogICAgICAgICAgICAgICAgICBtZXRob2Q9ImxtIiwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sPWNvbnRyb2wsCiAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5wYXNzKQoKIyBWaWV3IHRoZSByZXN1bHRzCnByaW50KG1vZGVsX2N2KQoKbWFlX3ZhbHVlcyA8LSBhcy5kYXRhLmZyYW1lKG1vZGVsX2N2JHJlc2FtcGxlJE1BRSkKbmFtZXMobWFlX3ZhbHVlcykgPC0gIk1BRSIKIAptZWFuX21hZSA8LSBtZWFuKG1hZV92YWx1ZXMkTUFFKQpzdGRfbWFlIDwtIHNkKG1hZV92YWx1ZXMkTUFFKQoKZ2dwbG90KGRhdGEgPSBtYWVfdmFsdWVzLCBhZXMoeCA9IE1BRSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMDAxKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbl9tYWUsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbl9tYWUgKyBzdGRfbWFlLCBjb2xvciA9ICJncmVlbiIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbl9tYWUgLSBzdGRfbWFlLCBjb2xvciA9ICJncmVlbiIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKQoKCmBgYAo8YnI+CkhlcmUgaXMgYSBoaXN0b2dyYW0gb2YgbWVhbiBhYnNvbHV0ZSBlcnJvciwgd2l0aCB2ZXJ0aWNhbCBsaW5lcyBmb3IgbWVhbiAocmVkKSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIChncmVlbikuIEEgdGlnaHRlciBoaXN0b2dyYW0gaGVyZSB3b3VsZCByZXByZXNlbnQgZ3JlYXRlciBnZW5lcmFsaXphYmlsaXR5IG9mIHRoZSBtb2RlbC4gVGhlIHNwcmVhZCBvZiB0aGlzIGhpc3RvZ3JhbSBpbmRpY2F0ZXMgZGVjZW50IGdlbmVyYWxpemFiaWxpdHksIGJ1dCBpdCBjb3VsZCBiZSBiZXR0ZXIuCgojIENvbmNsdXNpb24KIyMgSW50ZXJwcmV0aW5nIG91ciBwcmVkaWN0aW9ucwoKIyMjIENvbmNsdWRlIHdpdGggaG93IHVzZWZ1bCB5b3VyIGFsZ29yaXRobSBpcyBmb3IgdGhlIGJpa2UgcmUtYmFsYW5jaW5nIHBsYW4uCjxicj4KT3VyIG1vZGVsIGlzIG5vdCBncmVhdCBhIGNhcHR1cmluZyBwZWFrIHRpbWVzLCBhbmQgaXQgaXNuJ3QgcGFydGljdWxhcmx5IGdlbmVyYWxpemFibGUuIEhvd2V2ZXIsIGl0IGRvZXMgY2FwdHVyZSB0aGUgc3BhdGlhbCBhbmQgdGVtcG9yYWwgZGltZW5zaW9ucyBvZiBiaWtlc2hhcmUgZGVtYW5kLCB3aGljaCBhcmUgYSBnb29kIHN0YXJ0aW5nIHBvaW50IGZvciBiaWtlIGJhbGFuY2luZywgYXNzdW1pbmcgdGhhdCB0aGUgbW9kZWwgY29udGludWVzIHRvIGJlIGltcHJvdmVkIG92ZXIgdGltZS4gU29tZSB3YXlzIHRoYXQgdGhlIG1vZGVsIGNvdWxkIGJlIGltcHJvdmVkIGFyZSB0aHJvdWdoIGVuZ2luZWVyaW5nIG9mIHRlbXBvcmFsbHktdmFyaWFibGUgYW1lbml0aWVzIGluIHRoZSBmb3JtIG9mIGJ1c2luZXNzZXMsIG11c2V1bXMsIGFuZCByZXN0YXVyYW50cyBlbmNvZGVkIHNwYXRpYWxseSBidXQgYWxzbyB3aXRoIHRoZSBob3VycyB0aGV5IGFyZSBvcGVuIGFuZCBjbG9zZWQuCjxicj4KPGJyPgpPdXIgZGF0YSBhbHNvIGxvb2tzIGF0IG9ubHkgTWF5IHRvIEp1bmUuIEdpdmVuIHRoZSBoaWdoIG51bWJlciBvZiBoaWdoZXIgZWR1Y2F0aW9uIGluc3RpdHV0aW9ucyBpbiBQaGlsbHksIGl0IGlzIGltcG9ydGFudCB0byBjb25zaWRlciBob3cgdGhlIGRpcCBpbiB0aGUgY2l0eSdzIHBvcHVsYXRpb24gd2lsbCBiZSByZWZsZWN0ZWQgaW4gcmlkZXJzaGlwLCBwYXJ0aWN1bGFybHkgaW4gYXJlYXMgb2YgdGhlIGNpdHkgd2l0aCBoaWdoZXIgcHJvcG9ydGlvbnMgb2Ygc3R1ZGVudCBob3VzaW5nIGFuZCB1bml2ZXJzaXR5IGJ1aWxkaW5ncy4gRW5naW5lZXJpbmcgZmVhdHVyZXMgdG8gcmVmbGVjdCB0aGlzIGZsdWN0dWF0aW9uIHdvdWxkIGxpa2VseSBpbXByb3ZlIG1vZGVsIGFjY3VyYWN5LiBJdCBpcyBhbHNvIGltcG9ydGFudCB0byBjb25zaWRlciBob3cgYmlrZXNoYXJlIGRlbWFuZCBjaGFuZ2VzIGFjcm9zcyB0aGUgc2Vhc29ucy4gVGhlcmVmb3JlLCBiZWZvcmUgdGhpcyBtb2RlbCBpcyB1c2VkIHRvIGluZm9ybSBiaWtlIHJlLWJhbGFuY2luZyBiZXlvbmQgTWF5LUp1bmUsIGl0IHNob3VsZCBiZSB0cmFpbmVkIG9uIGRhdGEgZ2F0aGVyZWQgYWNyb3NzIGFuIGVudGlyZSB5ZWFyLgo=